/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file at <http://code.google.com/p/space-chronicles/>.
 */
package space.gl;

import java.nio.ByteBuffer;
import javax.media.opengl.GL3bc;
import jinngine.math.Matrix3;
import jinngine.math.Matrix4;
import jinngine.math.Vector3;
import space.glsl.GLSLDirectionalLight;
import space.math.Vector3f;

/**
 *
 * @author Pierre
 */
public class RenderContext {

    public Matrix4 getWorldView() {
       return worldView;
    }

    public enum Pass {
        SHADOW, SHADE, ADDITIVE, DEBUG
    };
    final private GL3bc gl;
    final public Matrix4 objectWorld;
    final public Matrix4 worldView;
    final private Matrix4 viewProjection;
    final private GLSLDirectionalLight light;
    private Program program;
    private Pass pass;

    public RenderContext(final GL3bc gl, final Program program,
            final GLSLDirectionalLight light,
            final Matrix4 viewProjection, final Matrix4 worldView,
            final Matrix4 objectWorld, final Pass pass) {
        if (gl == null) {
            throw new IllegalArgumentException();
        }
        if (objectWorld == null) {
            throw new IllegalArgumentException();
        }
        if (worldView == null) {
            throw new IllegalArgumentException();
        }
        if (viewProjection == null) {
            throw new IllegalArgumentException();
        }
        if (program == null) {
            throw new IllegalArgumentException();
        }
        if (light == null) {
            throw new NullPointerException();
        }
        if (pass == null) {
            throw new NullPointerException();
        }
        this.gl = gl;
        this.objectWorld = new Matrix4(objectWorld);
        this.worldView = new Matrix4(worldView);
        this.viewProjection = new Matrix4(viewProjection);
        this.light = light;
        this.program = program;
        this.pass = pass;

    }

    public RenderContext create() {
        return new RenderContext(gl, program, light, viewProjection, worldView, objectWorld, pass);
    }

    public void compose(Matrix4 matrix) {
        Matrix4.multiply(objectWorld, matrix, objectWorld);
    }

    public void applyLegacyMatrix() {
        gl.glMatrixMode(GL3bc.GL_PROJECTION);
        gl.glLoadMatrixd(viewProjection.toArray(), 0);
        gl.glMatrixMode(GL3bc.GL_MODELVIEW);
        gl.glLoadMatrixd(worldView.toArray(), 0);
        gl.glMultMatrixd(objectWorld.toArray(), 0);
    }

    public void worldToObject(final Vector3 direction, final Vector3 osDirection) {
        final Matrix3 worldObject = objectWorld.toMatrix3().assignTranspose();
        Matrix3.multiply(worldObject, direction, osDirection);
    }

    public GL3bc gl() {
        return gl;
    }

    public void applyAllUniforms() {
        light.applyUniform(this);
        applyUniform4fv("objectWorld", objectWorld);
        final Matrix4 objectWorldViewProjection = viewProjection.multiply(worldView).assignMultiply(
                objectWorld);
        applyUniform4fv("objectWorldViewProjection", objectWorldViewProjection);

        final Matrix4 viewObjectWorld = viewProjection.multiply(worldView).inverse();
        applyUniform3f("osEyePosition", (float) viewObjectWorld.a14,
                (float) viewObjectWorld.a24, (float) viewObjectWorld.a34);
    }

    public void applyUniform3f(String name, Vector3f v) {
        program.applyUniform3f(gl, name, v);
    }

    public void applyUniform3f(String name, float x, float y, float z) {
        program.applyUniform3f(gl, name, x, y, z);
    }

    public void applyUniform3f(String name, Vector3 v) {
        program.applyUniform3f(gl, name, v);
    }

    private void applyUniform4fv(String name, Matrix4 matrix) {
        program.applyUniform4fv(gl, name, matrix);
    }

    public void applyUniform1i(String name, int i) {
        program.applyUniform1i(gl, name, i);
    }

    public void applyUniform1f(String name, float f) {
        program.applyUniform1f(gl, name, f);
    }

    public void setViewProjection(Matrix4 m) {
        viewProjection.assign(m);
    }

    public void setWorldView(Matrix4 m) {
        worldView.assign(m);
    }

    public void setObjectWorld(Matrix4 m) {
        objectWorld.assign(m);
    }

    public void startUsingProgram() {
        program.startUsingProgram(gl);
    }

    public void stopUsingProgram() {
        program.stopUsingProgram(gl);
    }

    public void applyVertexAttribPointer(String locationName, int size, int type, boolean normalized, int stride, ByteBuffer buffer) {
        final int location = program.getAttribLocation(gl, locationName);
        if (location != -1) {
            gl.glVertexAttribPointer(location, size, type, normalized, stride, buffer);
            gl.glEnableVertexAttribArray(location);
        }
    }

    public void applyVertexAttribPointer(int location, int size, int type, boolean normalized, int stride, ByteBuffer buffer) {
        gl.glVertexAttribPointer(location, size, type, normalized, stride, buffer);
        gl.glEnableVertexAttribArray(location);
    }

    public void glDisableVertexAttribArray(String locationName) {

        final int location = program.getAttribLocation(gl, locationName);
        if (location != -1) {
            gl.glDisableVertexAttribArray(location);
        }

    }

    public void glDisableVertexAttribArray(int location) {
        gl.glDisableVertexAttribArray(location);
    }

    public double glInvertScale(Vector3 o) {
        Vector3 w = objectWorld.multiply(o);
        Vector3 v = worldView.multiply(w);
        return v.x * viewProjection.a41 + v.y * viewProjection.a42 + v.z * viewProjection.a43;

    }

    public Pass pass() {
        return pass;
    }

    public void setProgram(Program trailShader) {
        if (trailShader == null) {
            throw new NullPointerException();
        }
        program = trailShader;
    }
}
